/*
 * Copyright (c) 2011-2013, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package boofcv.alg.transform.wavelet;

import boofcv.misc.AutoTypeImage;
import boofcv.misc.CodeGeneratorBase;

import java.io.FileNotFoundException;


/**
 * @author Peter Abeles
 */
public class GenerateWaveletTransformOps extends CodeGeneratorBase {
	String className = "WaveletTransformOps";

	AutoTypeImage imageIn;
	AutoTypeImage imageOut;
	String genName;
	String sumType;
	String bitWise;
	String outputCast;
	String MIN_VALUE,MAX_VALUE;

	public GenerateWaveletTransformOps() throws FileNotFoundException {
		setOutputFile(className);
	}

	@Override
	public void generate() throws FileNotFoundException {
		printPreamble();

		printFuncs(AutoTypeImage.F32, AutoTypeImage.F32);
		printFuncs(AutoTypeImage.S32, AutoTypeImage.S32);

		out.print("\n" +
				"}\n");
	}

	private void printPreamble() {
		out.print("import boofcv.alg.InputSanityCheck;\n" +
				"import boofcv.alg.misc.PixelMath;\n" +
				"import boofcv.alg.transform.wavelet.impl.ImplWaveletTransformBorder;\n" +
				"import boofcv.alg.transform.wavelet.impl.ImplWaveletTransformInner;\n" +
				"import boofcv.alg.transform.wavelet.impl.ImplWaveletTransformNaive;\n" +
				"import boofcv.struct.image.ImageFloat32;\n" +
				"import boofcv.struct.image.ImageSInt32;\n" +
				"import boofcv.struct.wavelet.WaveletDescription;\n" +
				"import boofcv.struct.wavelet.WlCoef_F32;\n" +
				"import boofcv.struct.wavelet.WlCoef_I32;\n" +
				"\n" +
				"/**\n" +
				" * <p>\n" +
				" * Functional interface for applying general purpose wavelet and inverse wavelet transforms.\n" +
				" * </p>\n" +
				" *\n" +
				" * <p>\n" +
				" * A single level wavelet transform breaks the image up into four regions:\n" +
				" * <table border=\"1\">\n" +
				" * <tr><td>a</td><td>h</td></tr>\n" +
				" * <tr><td>v</td><td>d</td></tr>\n" +
				" * </table>\n" +
				" * Each region has M/2,N/2 rows and columns. Region 'a' is the scaling image, 'h' and 'v' are\n" +
				" * a combination of scaling and wavelet, and 'd' is a combination of horizontal and vertical wavelets.\n" +
				" * When a multiple level transform is performed then the input to the next level is the 'a' from the previous\n" +
				" * level.\n" +
				" * </p>\n" +
				" *\n" +
				" * <p>\n" +
				" * DO NOT MODIFY: This class was automatically generated by {@link "+getClass().getName()+"}\n" +
				" * </p>\n" +
				" *\n" +
				" * @author Peter Abeles\n" +
				" */\n" +
				"public class "+className+" {\n\n");
	}

	private void printFuncs( AutoTypeImage imageIn , AutoTypeImage imageOut ) {
		this.imageIn = imageIn;
		this.imageOut = imageOut;

		if( imageIn.isInteger() )
			genName = "I32";
		else
			genName = "F"+imageIn.getNumBits();

		sumType = imageIn.getSumType();
		bitWise = imageIn.getBitWise();

		if( sumType.compareTo(imageOut.getDataType()) == 0 ) {
			outputCast = "";
		} else {
			outputCast = "("+imageOut.getDataType()+")";
		}

		if( imageOut.isInteger() ) {
			MIN_VALUE = "Integer.MIN_VALUE";
			MAX_VALUE = "Integer.MAX_VALUE";
		} else {
			MIN_VALUE = "-Float.MAX_VALUE";
			MAX_VALUE = "Float.MAX_VALUE";
		}

		printTransform1();
		printTransformN();
		printInvert1();
		printInvertN();
	}

	private void printTransform1() {
		out.print("\t/**\n" +
				"\t * <p>\n" +
				"\t * Performs a single level wavelet transform.\n" +
				"\t * </p>\n" +
				"\t *\n" +
				"\t * @param desc Description of the wavelet.\n" +
				"\t * @param input Input image. Not modified.\n" +
				"\t * @param output Where the wavelet transform is written to. Modified.\n" +
				"\t * @param storage Optional storage image.  Should be the same size as output image. If null then\n" +
				"\t * an image is declared internally.\n" +
				"\t */\n" +
				"\tpublic static void transform1( WaveletDescription<WlCoef_"+genName+"> desc ,\n" +
				"\t\t\t\t\t\t\t\t   "+imageIn.getSingleBandName()+" input , "+imageOut.getSingleBandName()+" output ,\n" +
				"\t\t\t\t\t\t\t\t   "+imageOut.getSingleBandName()+" storage )\n" +
				"\t{\n" +
				"\t\tUtilWavelet.checkShape(input,output);\n" +
				"\n" +
				"\t\tWlCoef_"+genName+" coef = desc.getForward();\n" +
				"\n" +
				"\t\tif( output.width < coef.scaling.length || output.width < coef.wavelet.length )\n" +
				"\t\t\tthrow new IllegalArgumentException(\"Wavelet is too large for provided image.\");\n" +
				"\t\tif( output.height < coef.scaling.length || output.height < coef.wavelet.length )\n" +
				"\t\t\tthrow new IllegalArgumentException(\"Wavelet is too large for provided image.\");\n" +
				"\t\tstorage = InputSanityCheck.checkDeclare(output, storage);\n" +
				"\n" +
				"\t\t// the faster routines can only be run on images which are not too small\n" +
				"\t\tint minSize = Math.max(coef.getScalingLength(),coef.getWaveletLength())*3;\n" +
				"\n" +
				"\t\tif( input.getWidth() <= minSize || input.getHeight() <= minSize ) {\n" +
				"\t\t\tImplWaveletTransformNaive.horizontal(desc.getBorder(),coef,input,storage);\n" +
				"\t\t\tImplWaveletTransformNaive.vertical(desc.getBorder(),coef,storage,output);\n" +
				"\t\t} else {\n" +
				"\t\t\tImplWaveletTransformInner.horizontal(coef,input,storage);\n" +
				"\t\t\tImplWaveletTransformBorder.horizontal(desc.getBorder(),coef,input,storage);\n" +
				"\t\t\tImplWaveletTransformInner.vertical(coef,storage,output);\n" +
				"\t\t\tImplWaveletTransformBorder.vertical(desc.getBorder(),coef,storage,output);\n" +
				"\t\t}\n" +
				"\t}\n\n");
	}

	private void printTransformN() {
		out.print("\t/**\n" +
				"\t * <p>\n" +
				"\t * Performs a level N wavelet transform using the fast wavelet transform (FWT).\n" +
				"\t * </p>\n" +
				"\t *\n" +
				"\t * <p>To save memory the input image is used to store intermediate results and is modified.</p>\n" +
				"\t *\n" +
				"\t * @param desc Description of the wavelet.\n" +
				"\t * @param input Input image and is used as internal workspace. Modified.\n" +
				"\t * @param output Where the multilevel wavelet transform is written to. Modified.\n" +
				"\t * @param storage Optional storage image.  Should be the same size as output image. If null then\n" +
				"\t * an image is declared internally.\n" +
				"\t * @param numLevels Number of levels which should be computed in the transform.\n" +
				"\t */\n" +
				"\tpublic static void transformN( WaveletDescription<WlCoef_"+genName+"> desc ,\n" +
				"\t\t\t\t\t\t\t\t   "+imageIn.getSingleBandName()+" input , "+imageOut.getSingleBandName()+" output ,\n" +
				"\t\t\t\t\t\t\t\t   "+imageOut.getSingleBandName()+" storage ,\n" +
				"\t\t\t\t\t\t\t\t   int numLevels )\n" +
				"\t{\n" +
				"\t\tif( numLevels == 1 ) {\n" +
				"\t\t\ttransform1(desc,input,output, storage);\n" +
				"\t\t\treturn;\n" +
				"\t\t}\n" +
				"\n" +
				"\t\tUtilWavelet.checkShape(desc.getForward(),input,output,numLevels);\n" +
				"\t\tstorage = InputSanityCheck.checkDeclare(output, storage);\n" +
				"\t\t// modify the shape of a temporary image not the original\n" +
				"\t\tstorage = storage.subimage(0,0,output.width,output.height);\n" +
				"\t\tstorage.subImage = false;\n" +
				"\n" +
				"\t\ttransform1(desc,input,output, storage);\n" +
				"\n" +
				"\t\tfor( int i = 2; i <= numLevels; i++ ) {\n" +
				"\t\t\tint width = output.width/2;\n" +
				"\t\t\tint height = output.height/2;\n" +
				"\t\t\twidth += width%2;\n" +
				"\t\t\theight += height%2;\n" +
				"\n" +
				"\t\t\tinput = input.subimage(0,0,width,height);\n" +
				"\t\t\toutput = output.subimage(0,0,width,height);\n" +
				"\t\t\tinput.setTo(output);\n" +
				"\n" +
				"\t\t\t// transform the scaling image and save the results in the output image\n" +
				"\t\t\tstorage.reshape(width,height);\n" +
				"\t\t\ttransform1(desc,input,output,storage);\n" +
				"\t\t}\n" +
				"\t}\n\n");
	}

	private void printInvert1() {
		String primitive = imageOut.getDataType();

		out.print("\t/**\n" +
				"\t * <p>\n" +
				"\t * Performs a single level inverse wavelet transform. Do not pass in a whole image which has been\n" +
				"\t * transformed by a multilevel transform.  Just the relevant sub-image.\n" +
				"\t * </p>\n" +
				"\t *\n" +
				"\t * @param desc Description of the inverse wavelet.\n" +
				"\t * @param input Input wavelet transform. Not modified.\n" +
				"\t * @param output Reconstruction of original image. Modified.\n" +
				"\t * @param storage Optional storage image.  Should be the same size as the input image. If null then\n" +
				"\t * an image is declared internally.\n" +
				"\t * @param minValue Minimum allowed pixel value\n" +
				"\t * @param maxValue Maximum allowed pixel value\n" +
				"\t */\n" +
				"\tpublic static void inverse1( WaveletDescription<WlCoef_"+genName+"> desc ,\n" +
				"\t\t\t\t\t\t\t\t "+imageOut.getSingleBandName()+" input , "+imageIn.getSingleBandName()+" output ,\n" +
				"\t\t\t\t\t\t\t\t "+imageIn.getSingleBandName()+" storage , "+primitive+" minValue , "+primitive+" maxValue )\n" +
				"\t{\n" +
				"\t\tUtilWavelet.checkShape(output,input);\n" +
				"\t\tWlCoef_"+genName+" coef = desc.getForward();\n" +
				"\t\tif( output.width < coef.scaling.length || output.width < coef.wavelet.length )\n" +
				"\t\t\tthrow new IllegalArgumentException(\"Wavelet is too large for provided image.\");\n" +
				"\t\tif( output.height < coef.scaling.length || output.height < coef.wavelet.length )\n" +
				"\t\t\tthrow new IllegalArgumentException(\"Wavelet is too large for provided image.\");\n" +
				"\t\tstorage = InputSanityCheck.checkDeclare(input, storage);\n" +
				"\n" +
				"\t\t// the faster routines can only be run on images which are not too small\n" +
				"\t\tint minSize = Math.max(coef.getScalingLength(),coef.getWaveletLength())*3;\n" +
				"\n" +
				"\t\tif( output.getWidth() <= minSize || output.getHeight() <= minSize ) {\n" +
				"\t\t\tImplWaveletTransformNaive.verticalInverse(desc.getBorder(),desc.getInverse(),input,storage);\n" +
				"\t\t\tImplWaveletTransformNaive.horizontalInverse(desc.getBorder(),desc.getInverse(),storage,output);\n" +
				"\t\t} else {\n" +
				"\t\t\tImplWaveletTransformInner.verticalInverse(desc.getInverse().getInnerCoefficients(),input,storage);\n" +
				"\t\t\tImplWaveletTransformBorder.verticalInverse(desc.getBorder(),desc.getInverse(),input,storage);\n" +
				"\t\t\tImplWaveletTransformInner.horizontalInverse(desc.getInverse().getInnerCoefficients(),storage,output);\n" +
				"\t\t\tImplWaveletTransformBorder.horizontalInverse(desc.getBorder(),desc.getInverse(),storage,output);\n" +
				"\t\t}\n" +
				"\n" +
				"\t\tif( minValue != "+MIN_VALUE+" && maxValue != "+MAX_VALUE+" )\n" +
				"\t\t\tPixelMath.boundImage(output,minValue,maxValue);\n" +
				"\t}\n\n");
	}

	private void printInvertN() {
		String primitive = imageOut.getDataType();

		out.print("\t/**\n" +
				"\t * <p>Performs a level N inverse fast wavelet transform (FWT).</p>\n" +
				"\t *\n" +
				"\t * <p>To save memory the input image is used to store intermediate results and is modified.</p>\n" +
				"\t *\n" +
				"\t * @param desc Description of the inverse wavelet.\n" +
				"\t * @param input Input wavelet transform and is used as internal workspace. Modified.\n" +
				"\t * @param output Reconstruction of original image. Modified.\n" +
				"\t * @param storage Optional storage image.  Should be the same size as the input image. If null then\n" +
				"\t * an image is declared internally.\n" +
				"\t * @param numLevels Number of levels in the transform.\n" +
				"\t * @param minValue Minimum allowed pixel value\n" +
				"\t * @param maxValue Maximum allowed pixel value\n" +
				"\t */\n" +
				"\tpublic static void inverseN( WaveletDescription<WlCoef_"+genName+"> desc ,\n" +
				"\t\t\t\t\t\t\t\t "+imageOut.getSingleBandName()+" input , "+imageIn.getSingleBandName()+" output ,\n" +
				"\t\t\t\t\t\t\t\t "+imageOut.getSingleBandName()+" storage,\n" +
				"\t\t\t\t\t\t\t\t int numLevels ,\n" +
				"\t\t\t\t\t\t\t\t "+primitive+" minValue , "+primitive+" maxValue)\n" +
				"\t{\n" +
				"\t\tif( numLevels == 1 ) {\n" +
				"\t\t\tinverse1(desc,input,output, storage,minValue,maxValue);\n" +
				"\t\t\tPixelMath.boundImage(output,minValue,maxValue);\n" +
				"\t\t\treturn;\n" +
				"\t\t}\n" +
				"\n" +
				"\t\tUtilWavelet.checkShape(desc.getForward(),output,input,numLevels);\n" +
				"\t\tstorage = InputSanityCheck.checkDeclare(input, storage);\n" +
				"\t\t// modify the shape of a temporary image not the original\n" +
				"\t\tstorage = storage.subimage(0,0,input.width,input.height);\n" +
				"\t\tstorage.subImage = false;\n" +
				"\n" +
				"\t\tint width,height;\n" +
				"\n" +
				"\t\tint scale = UtilWavelet.computeScale(numLevels);\n" +
				"\t\twidth = input.width/scale;\n" +
				"\t\theight = input.height/scale;\n" +
				"\t\twidth += width%2;\n" +
				"\t\theight += height%2;\n" +
				"\n" +
				"\t\t"+imageOut.getSingleBandName()+" levelIn = input.subimage(0,0,width,height);\n" +
				"\t\t"+imageIn.getSingleBandName()+" levelOut = output.subimage(0,0,width,height);\n" +
				"\t\tstorage.reshape(width,height);\n" +
				"\t\tinverse1(desc,levelIn,levelOut, storage,"+MIN_VALUE+","+MAX_VALUE+");\n" +
				"\n" +
				"\t\tfor( int i = numLevels-1; i >= 1; i-- ) {\n" +
				"\t\t\t// copy the decoded segment into the input\n" +
				"\t\t\tlevelIn.setTo(levelOut);\n" +
				"\t\t\tif( i > 1 ) {\n" +
				"\t\t\t\tscale /= 2;\n" +
				"\t\t\t\twidth = input.width/scale;\n" +
				"\t\t\t\theight = input.height/scale;\n" +
				"\t\t\t\twidth += width%2;\n" +
				"\t\t\t\theight += height%2;\n" +
				"\n" +
				"\t\t\t\tstorage.reshape(width,height);\n" +
				"\t\t\t\tlevelIn = input.subimage(0,0,width,height);\n" +
				"\t\t\t\tlevelOut = output.subimage(0,0,width,height);\n" +
				"\t\t\t} else {\n" +
				"\t\t\t\tlevelIn = input;\n" +
				"\t\t\t\tlevelOut = output;\n" +
				"\t\t\t}\n" +
				"\n" +
				"\t\t\tstorage.reshape(levelIn.width,levelIn.height);\n" +
				"\t\t\tinverse1(desc,levelIn,levelOut, storage,"+MIN_VALUE+","+MAX_VALUE+");\n" +
				"\t\t}\n" +
				"\n" +
				"\t\tif( minValue != "+MIN_VALUE+" && maxValue != "+MAX_VALUE+" )\n" +
				"\t\t\tPixelMath.boundImage(output, minValue, maxValue);\n" +
				"\t}\n\n");
	}

	public static void main( String args[] ) throws FileNotFoundException {
		GenerateWaveletTransformOps app = new GenerateWaveletTransformOps();
		app.generate();
	}
}
